package com.yukefms.engine;

import com.yukefms.common.Counter;
import com.yukefms.engine.interfaces.PositionCacheStructure;
import com.yukefms.common.Timer;
import com.yukefms.engine.running.*;
import com.yukefms.jointpoint.BodyPointCloud;
import com.yukefms.jointpoint.interfaces.Position;
import com.yukefms.message.MessageProducer;
import com.yukefms.rule.interfaces.BARule;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description
 * @Author Ping
 * @Date 2021/6/29 12:05
 **/
public class DefaultRunningContext implements RunningContext {

    protected String bId ;
    protected BARule runningRule ;
    protected String runningRuleName ;
    protected List<String> involvedJointTypes ;
    protected Timer timeoutTimer ;
    protected Counter inputErrorCounter ;
    protected Counter trialoutCounter ;
    protected Counter invariantFailCounter ;
    protected Counter performingFailCounter ;
    protected Map<EventCode, RunningEventListener> listenerMap ;
    protected MessageProducer producer ;
    protected PositionCacheStructure pointCloudCacheStructure ;
    protected double currentScore ;
    private BodyPointCloud lastPointCloud ;
    private boolean hasLastPointCloud = false ;
    /**
     * listeners for timeout, inputerror, trialout, success events
     *
     */
    RunningEventListener timeoutEventListener,
                         inputErrorEventListener,
                         trialOutEventListener,
                         successEventListener,
                        runningEventListener ;



    public DefaultRunningContext(final BARule rule) {
        if (rule != null) {
            this.runningRule = rule;
            this.runningRuleName = rule.ruleName();
            this.involvedJointTypes = rule.getInvolvedJointTypes();
        }
        timeoutTimer = new Timer(EngineConfig.TIME_PERIOD_MS) ;
        inputErrorCounter = new Counter(EngineConfig.TOLERANT_NUM_OF_INPUT_ERROR) ;
        trialoutCounter = new Counter(EngineConfig.TRIAL_TIME) ;
        invariantFailCounter = new Counter(EngineConfig.INVARIANT_FAIL_NUM) ;
        performingFailCounter = new Counter(EngineConfig.PERFORM_FAIL_NUM) ;
        listenerMap = new HashMap<>() ;
        this.timeoutEventListener = new TimeoutEventListener() ;
        this.inputErrorEventListener = new InputErrorEventListener() ;
        this.trialOutEventListener = new TrialoutEventListener() ;
        this.successEventListener = new SuccessEventListener() ;
        this.runningEventListener = new DefaultEventListener() ;
        listenerMap.put(EventCode.TIME_OUT,  this.timeoutEventListener) ;
        listenerMap.put(EventCode.INPUT_ERROR, this.inputErrorEventListener) ;
        listenerMap.put(EventCode.TRIAL_OUT, this.trialOutEventListener) ;
        listenerMap.put(EventCode.SUCCESS, this.successEventListener) ;
        listenerMap.put(EventCode.ACTIVED, this.runningEventListener) ;
        pointCloudCacheStructure = new DefaultPositionCacheStructure() ;
    }

    public DefaultRunningContext(final BARule rule, MessageProducer producer) {
       if (rule != null) {
           this.runningRule = rule;
           this.runningRuleName = rule.ruleName();
           this.involvedJointTypes = rule.getInvolvedJointTypes();
       }
        timeoutTimer = new Timer(EngineConfig.TIME_PERIOD_MS) ;
        inputErrorCounter = new Counter(EngineConfig.TOLERANT_NUM_OF_INPUT_ERROR) ;
        trialoutCounter = new Counter(EngineConfig.TRIAL_TIME) ;
        invariantFailCounter = new Counter(EngineConfig.INVARIANT_FAIL_NUM) ;
        performingFailCounter = new Counter(EngineConfig.PERFORM_FAIL_NUM) ;
        listenerMap = new HashMap<>() ;
        this.timeoutEventListener = new ToKafkaListenerDecorator(new TimeoutEventListener(), producer) ;
        this.inputErrorEventListener = new ToKafkaListenerDecorator(new InputErrorEventListener(), producer) ;
        this.trialOutEventListener = new ToKafkaListenerDecorator(new TrialoutEventListener(), producer) ;
        this.successEventListener = new ToKafkaListenerDecorator(new SuccessEventListener(), producer) ;
        this.runningEventListener = new ToKafkaListenerDecorator(new DefaultEventListener(), producer) ;
        listenerMap.put(EventCode.TIME_OUT,  this.timeoutEventListener) ;
        listenerMap.put(EventCode.INPUT_ERROR, this.inputErrorEventListener) ;
        listenerMap.put(EventCode.TRIAL_OUT, this.trialOutEventListener) ;
        listenerMap.put(EventCode.SUCCESS, this.successEventListener) ;
        listenerMap.put(EventCode.ACTIVED, this.runningEventListener) ;
        pointCloudCacheStructure = new DefaultPositionCacheStructure() ;
    }


    @Override
    public List<String> relevantJointTypes() {
        return this.involvedJointTypes;
    }

    @Override
    public String bodyId() {
        return this.bId;
    }

    @Override
    public void setBodyId(String bId) {
        this.bId = bId ;
    }

    @Override
    public String ruleName() {
        return this.runningRuleName;
    }


    @Override
    public boolean isTimeout() {
        return this.timeoutTimer.isExpired();
    }

    @Override
    public boolean isTrialout() {
        return this.trialoutCounter.isOverMax();
    }

    @Override
    public boolean isInputError() {
        return this.inputErrorCounter.isOverMax();
    }

    @Override
    public void resetTimer() {
        this.timeoutTimer.reset();
    }

    @Override
    public void resetInputErrorCounter() {
        this.inputErrorCounter.reset();
    }

    @Override
    public void resetTrialoutCounter() {
        this.trialoutCounter.reset();
    }

    @Override
    public void resetInvariantFailCounter() {
        this.invariantFailCounter.reset();
    }

    @Override
    public void resetPerformingFailCounter() {
        this.performingFailCounter.reset() ;
    }

    @Override
    public Counter getPerformingFailCounter() {
        return this.performingFailCounter;
    }

    @Override
    public void add(InputErrorEventListener inputErrorListener) {
        this.listenerMap.put(EventCode.INPUT_ERROR, inputErrorListener) ;
    }

    @Override
    public void notifyInputErrorEventListener(RunningEvent event, RunningContext context) {
        this.listenerMap.get(EventCode.INPUT_ERROR).handle(event, this);
    }

    @Override
    public void add(TimeoutEventListener timeoutListener) {
        this.listenerMap.put(EventCode.TIME_OUT, timeoutListener) ;
    }

    @Override
    public void notifyTimeoutEventListener(RunningEvent event, RunningContext context) {
        this.listenerMap.get(EventCode.TIME_OUT).handle(event, this) ;
    }

    @Override
    public void add(TrialoutEventListener trialoutListener) {
        this.listenerMap.put(EventCode.TRIAL_OUT, trialoutListener) ;
    }

    @Override
    public void notifyTrialoutEventListener(RunningEvent event, RunningContext context) {
        this.listenerMap.get(EventCode.TRIAL_OUT).handle(event, this);
    }

    @Override
    public void add(FailedEventListener failedEventListener) {
        this.listenerMap.put(EventCode.UNMATCHED, failedEventListener) ;
    }

    @Override
    public void notifyfailedEventListener(RunningEvent event, RunningContext context) {
        this.listenerMap.get(EventCode.UNMATCHED).handle(event, this);
    }

    @Override
    public void add(SuccessEventListener successEventListener) {
        this.listenerMap.put(EventCode.SUCCESS, successEventListener) ;
    }

    @Override
    public void notifySuccessEventListener(RunningEvent event, RunningContext context) {
        this.listenerMap.get(EventCode.SUCCESS).handle(event, this);
    }


    @Override
    public Timer getTimeoutTimer() {
        return this.timeoutTimer;
    }

    @Override
    public Counter getInputErrorCounter() {
        return this.inputErrorCounter;
    }

    @Override
    public Counter getTrialoutCounter() {
        return this.trialoutCounter;
    }

    @Override
    public Position getLastPosition(String jointType) {
        BodyPointCloud pointCloud = this.pointCloudCacheStructure.getLast() ;
        if (pointCloud == null) {
            pointCloud = this.lastPointCloud ;
        } else {
            this.lastPointCloud = pointCloud ;
        }
        return pointCloud.getPosition(jointType);
    }

    @Override
    public Position getStartPosition(String jointType) {
        BodyPointCloud pointCloud = this.pointCloudCacheStructure.getStart() ;
        return pointCloud == null ? null : pointCloud.getPosition(jointType);
    }

    @Override
    public boolean hasCachedLastPosition() {
        return this.pointCloudCacheStructure.hasLast() ;
    }

    @Override
    public BARule getInvolvedRule() {
        return this.runningRule;
    }

    @Override
    public void setInvolvedRule(BARule involvedRule) {
        this.runningRule = involvedRule ;
    }

    @Override
    public Counter getInvariantFailCounter() {
        return this.invariantFailCounter;
    }

    @Override
    public void addRunningEventListener(RunningEventListener eventListener) {

    }

    @Override
    public void notifyRunningEventListener(EventCode eventCode, String actionName) {
        this.runningEventListener.handle(RunningEvent.buildEvent(eventCode, this.runningRuleName
                                           , actionName, this.currentScore),
                                        this);
    }

    @Override
    public void notifyRunningEventListener(EventCode eventCode) {
        this.runningEventListener.handle(RunningEvent.buildEvent(eventCode, this.runningRuleName),
                this);
    }

    @Override
    public void notifyRunningEventListener(EventCode eventCode, String actionName, String msg) {
        this.runningEventListener.handle(new RunningEvent(eventCode, this.runningRuleName,
                actionName, msg, this.currentScore), this) ;
    }

    @Override
    public void updateCachingPointCloud(BodyPointCloud pointCloud) {

        this.pointCloudCacheStructure.update(pointCloud);
    }

    @Override
    public void cacheTriggerStartPointCloud(BodyPointCloud pointCloud) {
        this.lastPointCloud = pointCloud ;
        this.pointCloudCacheStructure.cacheStart(pointCloud);
    }

    @Override
    public void updateScore(double score) {
        if (this.currentScore < score ) {
            this.currentScore = score ;
        }
    }

    @Override
    public double currentScore() {
        return this.currentScore;
    }

    @Override
    public void resetCachingStructure() {
        this.pointCloudCacheStructure.reset();
    }
}
